package s3client

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"errors"
	"fmt"
	s3action "gitee.com/lsy007/s3client/model"
	"github.com/golang/protobuf/proto"
	"github.com/smallnest/rpcx/client"
	"github.com/smallnest/rpcx/protocol"
)

func (c *Client) Request(model s3action.Model) (reply s3action.Reply, err error) {
	// 1. 组装请求头
	header := s3action.Header{
		RequestId: c.RequestId,
		ProName:   c.ProName,
		Region:    c.Region,
		Server:    c.ServerName,
		Other:     c.Other,
	}
	if len(c.RequestId) < 16 {
		err = fmt.Errorf("the length of the requestId must be greater than 16")
		return
	}
	// 2. proto转码请求数据模型
	byteModel, err := proto.Marshal(&model)
	if err != nil {
		return reply, err
	}
	// 3. 判断是否加密
	if c.Encrypt {
		iv := []byte(c.RequestId[0:aes.BlockSize])
		if byteModel, err = aesCTREncrypt(byteModel, []byte(c.Secret), iv); err != nil {
			return
		}
		header.Encrypt = "Enabled"
	}
	// 4. 执行 rpc 请求
	args := s3action.Args{Header: &header, Model: byteModel}
	err = SendRequest(c.Addr, "S3Handler", "Request", &args, &reply)
	if err != nil {
		return
	}
	// 5.判断中间件内部是否错误
	if reply.ErrMsg != "" {
		err = errors.New(reply.ErrMsg)
	}
	return
}

// 基于aes加密算法的ctr分组模式的加密
// key加解密秘钥，长度32字节；iv用proId截取长度16字节的值
func aesCTREncrypt(plainText, key, iv []byte) ([]byte, error) {
	// 创建des算法的接口
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	// 创建CTR分组模式下的接口
	stream := cipher.NewCTR(block, iv)
	// 使用XORKeyStream完成加密,可以创建dst参数，也可不创建，若要创建注意与填充后的明文等长
	cipherText := make([]byte, len(plainText))
	stream.XORKeyStream(cipherText, plainText)
	// 返回密文
	return cipherText, nil
}

func SendRequest(addr, registerName, method string, args interface{}, reply interface{}) (err error) {
	//loger.Debug("send request", zap.Any("server", server), zap.Any("args", args), zap.Any("reply", reply))
	c := client.NewPeer2PeerDiscovery(addr, "")
	option := client.DefaultOption
	option.SerializeType = protocol.ProtoBuffer // 设置编解码格式为 protobuf 格式
	rpcClient := client.NewXClient(registerName, client.Failtry, client.RandomSelect, c, option)
	defer rpcClient.Close()
	return rpcClient.Call(context.Background(), method, args, reply)
}
